home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / Book Chapters / 04 - File Management / Dungeon 3 / Dungeon3.c next >
Encoding:
C/C++ Source or Header  |  1995-06-13  |  26.5 KB  |  967 lines  |  [TEXT/MMCC]

  1.  
  2. /*Dungeon3 – prototype game example for the Mac Game book*/
  3. /*By Ingemar Ragnemalm 1995*/
  4. /**/
  5. /*This game is somewhat similar to MemoryGame, in that it uses a grid, represented*/
  6. /*by an array. The game is a typical (though extremely simplified) dungeon-digging game,*/
  7. /*where the objective is to collect treasures and fight monsters.*/
  8.  
  9. /*Representation:*/
  10. /*The array tileArr holds nearly all information we need. telling what is in each space in the grid.*/
  11. /*Monsters and treasures are only represented this way. In a real game, you may wish to*/
  12. /*keep a list of all monster and treasure positions, to avoid scanning for them and to keep more*/
  13. /*information about each.*/
  14. /*The game also keeps an array that tells what spaces are known to the player (tilesKnown), and*/
  15. /*the player position (playerPosition), so we don't have to scan for it all the time.*/
  16. /*The combat system is extremely simple. If you try moving to an enemy, you have 60% chance to*/
  17. /*hit it and kill it. If an enemy tries to move to you, it has 50% chance to hit, reducing your hit points*/
  18. /*by one.*/
  19. /**/
  20. /*The game ends when the player dies.*/
  21.  
  22. /*This version adds the following features over Dungeon2:*/
  23. /*- Save, Save as… Open…*/
  24. /*- Most of the variables in a record, to make it easy to save */
  25.  
  26.  
  27. #include <Sound.h>
  28.  
  29. /*Size of the array*/
  30. #define    kArraySizeH 15
  31. #define    kArraySizeV 12
  32.  
  33. /*Size of the tiles*/
  34. #define    kTileSizeH 32
  35. #define    kTileSizeV 32
  36.  
  37. /*Menu ids*/
  38. #define    appleID 127
  39. #define    fileID 128
  40.  
  41. /* A macro for taking the abs of a value */
  42. #define abs(x) (x>0?x:-x)
  43.  
  44. /* All the possible states of a tile (space in the dungeon) */
  45. typedef enum {empty, wall, player, enemy, tempEnemy, gold, exitPos} TileState;
  46.  
  47.  
  48. /*Variables defining the game state*/
  49. typedef struct
  50. {
  51.     TileState tileArray[kArraySizeH][kArraySizeV];    /* What is in each tile? */
  52.     Boolean tileKnown[kArraySizeH][kArraySizeV];    /* What tiles have we seen? */
  53.     Point playerPosition;                            /* Current position */
  54.     short playerHitPoints;                            /* How much health left? */
  55. } GameState;
  56.  
  57. typedef GameState **GameStateHnd;        /* Define a handle type to it */
  58.  
  59.  
  60. /* Define a handle for the game state */
  61. GameStateHnd game;
  62.     
  63. /* The window pointer */
  64. WindowPtr myWindow;
  65.  
  66. /* A boolean telling if we should quit yet or not */
  67. Boolean gDone = false;
  68.  
  69. /*Pictures*/
  70. PicHandle floorTile;
  71. PicHandle playerTile;
  72. PicHandle enemyTile;
  73. PicHandle goldTile;
  74. PicHandle wallTile;
  75. PicHandle exitTile;
  76.  
  77. /*All 8 directions as vectors*/
  78. Point directionTable[8] = { { 0, 1 },
  79.                           {-1, 1 },
  80.                           {-1, 0 },
  81.                           {-1,-1 },
  82.                           { 0,-1 },
  83.                           { 1,-1 },
  84.                           { 1, 0 },
  85.                           { 1, 1 }};
  86.  
  87.  
  88. /*The creator code should match the one of the application.*/
  89. /*The type should be defined in the "BNDL" resource in the app.*/
  90.  
  91. #define    saveFileCreator    '????'
  92. #define    saveFileType    'DSav'
  93.  
  94.  
  95.   StandardFileReply lastReply;
  96.  
  97. /*Save/save as:
  98. The boolean is true if we are required to show a StandardFile dialog.*/
  99. static void SaveGame(Boolean as)
  100. {
  101.    StandardFileReply reply;
  102.    SFTypeList typeList;
  103.    OSErr err;
  104.    short oldFile, fileRef;
  105.  
  106.   if ( ! as && (lastReply.sfGood) )
  107.    reply = lastReply;
  108.   else
  109.    StandardPutFile("\pSave the game in what file?", "\pDungeon save", &reply);
  110.  
  111.   if ( reply.sfGood )
  112.    {
  113.     oldFile = CurResFile ();
  114.  
  115. /* Delete in case there's an old file */
  116.     err = FSpDelete(&reply.sfFile);
  117.  
  118.     err = FSpCreate(&reply.sfFile, saveFileCreator, saveFileType, 0); /*smRoman = 0*/
  119.     FSpCreateResFile(&reply.sfFile, saveFileCreator, saveFileType, 0); /*smRoman = 0*/
  120.     fileRef = FSpOpenResFile(&reply.sfFile, fsRdWrPerm);
  121.     UseResFile(fileRef);
  122.     AddResource((Handle)game, 'Save', 0, "\pGame save information");
  123.     WriteResource((Handle)game);
  124.     DetachResource((Handle)game);
  125.     UseResFile(oldFile);
  126.     CloseResFile(fileRef);
  127.  
  128.     lastReply = reply;
  129.    };
  130.  }; /*SaveGame*/
  131.  
  132. /*Open game:
  133. The Boolean is true if we should show a stdfile dialog */
  134. static void OpenGame(Boolean as)
  135. {
  136.    StandardFileReply reply;
  137.    SFTypeList typeList;
  138.    OSErr err;
  139.    short oldFile, fileRef;
  140.  
  141.   if (as) {
  142.       typeList[0] = saveFileType;
  143.       StandardGetFile(nil, 1, typeList, &reply);
  144.       }
  145.   else
  146.      reply = lastReply;
  147.  
  148.   if ( reply.sfGood )
  149.    {
  150.     oldFile = CurResFile ();
  151.     fileRef = FSpOpenResFile(&reply.sfFile, fsRdPerm);
  152.     UseResFile(fileRef);
  153.     DisposeHandle((Handle)game);
  154.     game = (GameStateHnd)GetResource('Save', 0);
  155.     DetachResource((Handle)game);
  156.     UseResFile(oldFile);
  157.     CloseResFile(fileRef);
  158.     lastReply = reply;
  159.  
  160. /*Force an update of the entire window*/
  161.     SetPort(myWindow);
  162.     InvalRect(&myWindow->portRect);
  163.    };
  164.  }; /*OpenGame*/
  165.  
  166.  
  167. /* End of file management routines */
  168.  
  169.  
  170.  
  171. /* ******* MultiFinder and Apple events: ******* */
  172.  
  173. /*Handle the required Apple events:
  174. DoOpenApp: nothing special
  175. DoOpenDoc: open saved game
  176. DoPrintDoc: ignored
  177. DoQuitApp: quit
  178. */
  179.  
  180. /*MyGotRequiredParams: tells whether we have handled all we have to or not.*/
  181. static OSErr MyGotRequiredParams(const AppleEvent *theAppleEvent)
  182. {
  183.     DescType returnedType;
  184.     Size actualSize;
  185.  
  186.     if ( AEGetAttributePtr(theAppleEvent, keyMissedKeywordAttr, typeWildCard, &returnedType, nil, 0, &actualSize) == errAEDescNotFound )
  187.         return noErr;
  188.     else
  189.         return errAEParamMissed;
  190. };
  191.  
  192. /* "Open application" Apple Event received */
  193. static pascal OSErr DoOpenApp(const AppleEvent *theAppleEvent, AppleEvent *reply, long refCon)
  194. {
  195. /*What am I supposed to do here?*/
  196.         return MyGotRequiredParams(theAppleEvent);
  197. };
  198.  
  199. /* "Open document" Apple Event received */
  200. static pascal OSErr DoOpenDoc(const AppleEvent *theAppleEvent, AppleEvent *reply, long refCon)
  201. {
  202.         OSErr    err;
  203.         AEDescList    fileSpecList;
  204.         short    i;
  205.         long    count;
  206.         Size    actual;
  207.         FSSpec    desc;
  208.         AEKeyword    keyword;
  209.         DescType    type;
  210.  
  211.         err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList, &fileSpecList);
  212.         
  213.         err = AECountItems(&fileSpecList, &count);
  214.         for (i = 1; i <= count; i++) {
  215.             err = AEGetNthPtr(&fileSpecList, i, typeFSS, &keyword, &type, (Ptr)&desc, sizeof(FSSpec), &actual);
  216.             if (err == noErr) {
  217. /* Copy the file desciption into lastReply so Open can get it from there. */
  218.                 lastReply.sfFile = desc;
  219.                 lastReply.sfGood = true;
  220.                 OpenGame(false);
  221. /* Since we only allow one file at a time, let's exit DoOpenDoc once we have one. */
  222.                 return MyGotRequiredParams(theAppleEvent);
  223.                 }
  224.             }
  225.         return MyGotRequiredParams(theAppleEvent);
  226. }; /* DoOpenDoc */
  227.  
  228. /* "Print" Apple Event received */
  229. static pascal OSErr DoPrintDoc(const AppleEvent *theAppleEvent, AppleEvent *reply, long refCon)
  230. {
  231.     return errAEEventNotHandled; /*We don't print any documents!*/
  232. };
  233.  
  234. /* "Quit" Apple Event received */
  235. static pascal OSErr DoQuitApp(const AppleEvent *theAppleEvent, AppleEvent *reply, long refCon)
  236. {
  237.     gDone = true;            /*If I'm told to quit, I'll quit.*/
  238.     return MyGotRequiredParams(theAppleEvent);
  239. };
  240.  
  241.  
  242. /*Initialize Apple events*/
  243.  
  244. static void AppleEventInit()
  245. {
  246.         OSErr error;
  247.  
  248.         error = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, &DoOpenApp, 0, false);
  249.         error = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, &DoOpenDoc, 0, false);
  250.         error = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, &DoPrintDoc, 0, false);
  251.         error = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, &DoQuitApp, 0, false);
  252. /*I ignore errors.*/
  253. };
  254.  
  255. /* End of Apple Event handling */
  256.  
  257.  
  258. /* A function that generates a value in the interval 0..range-1 */
  259.  
  260. static short Rand(short range)
  261. {
  262.     return (Random () & 0x7fff) % range;
  263. }; /*Rand*/
  264.  
  265.  
  266. /* Draw a tile */
  267.  
  268. static void DrawTile(short h, short v)
  269. {
  270.     Rect tileRectangle;
  271.  
  272.     SetRect(&tileRectangle, h * kTileSizeH, v * kTileSizeV, (h + 1) * kTileSizeH, (v + 1) * kTileSizeV);
  273.     if ( (**game).tileKnown[h][v] )
  274.         switch ( (**game).tileArray[h][v] )
  275.         {
  276.             case empty: 
  277.                 DrawPicture(floorTile, &tileRectangle); break;
  278.             case wall: 
  279.                 DrawPicture(wallTile, &tileRectangle); break;
  280.             case player: 
  281.                 DrawPicture(playerTile, &tileRectangle); break;
  282.             case enemy:
  283.             case tempEnemy: 
  284.                 DrawPicture(enemyTile, &tileRectangle); break;
  285.             case gold: 
  286.                 DrawPicture(goldTile, &tileRectangle); break;
  287.             case exitPos: 
  288.                 DrawPicture(exitTile, &tileRectangle); break;
  289.             default:
  290.                 PaintRect(&tileRectangle);
  291.         }
  292.     else
  293.         PaintRect(&tileRectangle);
  294. } /*DrawTile*/
  295.  
  296.  
  297. /* Set all tiles around the player to be known, and draw them if they were not known before. */
  298.  
  299. static void ShowAroundPlayer()
  300. {
  301.     short h, v;
  302.  
  303.     for ( h = (**game).playerPosition.h - 1 ; h <= (**game).playerPosition.h + 1 ; h++)
  304.         for ( v = (**game).playerPosition.v - 1 ; v <= (**game).playerPosition.v + 1 ; v++)
  305.             if ( ! (**game).tileKnown[h][v] )
  306.                 {
  307.                     (**game).tileKnown[h][v] = true;
  308.                     DrawTile(h, v);
  309.                 }
  310. } /*ShowAroundPlayer*/
  311.  
  312.  
  313. /* The level generation routine */
  314.  
  315. static void CreateLevel()
  316. {
  317.     Rect roomRect[10];
  318.     short room;
  319.     short height, width;
  320.     short h, v, h1, v1, h2, v2;
  321.     short numRooms;
  322.     short numTreasures, numMonsters;
  323.     short i;
  324.  
  325. /*For each tile position, we set the initial state.*/
  326. /**/
  327. /*Here we generate the dungeon randomly, with a rather simple algorithm.*/
  328. /*Most real games use pre-designed levels, preferrably stored in resources.*/
  329.  
  330.  
  331. /*Dungeon generation algorithm:*/
  332. /*We select a number of rooms to be placed, 3-10*/
  333. /*Each room is randomly assigned a size, and then a position.*/
  334. /*In each room we may put monsters and/or treasures.*/
  335. /*From each room except the last, we make a path from the room to the next.*/
  336. /*This guarantees that all rooms are connected.*/
  337.  
  338.  
  339. /*First fill the entire dungeon with walls!*/
  340.         for ( h = 0 ; h < kArraySizeH ; h++)
  341.             for ( v = 0 ; v < kArraySizeV ; v++)
  342.                     (**game).tileArray[h][v] = wall;
  343.  
  344.         numRooms = 3 + Rand(4); /*3 to 6 rooms*/
  345.  
  346. /*Create each room*/
  347.         for ( room = 0 ; room <= numRooms - 1 ; room++)
  348.             {
  349.                 height = 1 + Rand(5 - numRooms / 2);
  350.                 width = 1 + Rand(5 - numRooms / 2);
  351.                 roomRect[room].top = 1 + Rand(kArraySizeV - height - 2);
  352.                 roomRect[room].bottom = roomRect[room].top + height;
  353.                 roomRect[room].left = 1 + Rand(kArraySizeH - width - 2);
  354.                 roomRect[room].right = roomRect[room].left + width;
  355.  
  356.                 for ( h = roomRect[room].left ; h <= roomRect[room].right ; h++)
  357.                     for ( v = roomRect[room].top ; v <= roomRect[room].bottom ; v++)
  358.                             (**game).tileArray[h][v] = empty;
  359.             };
  360.  
  361. /*Make paths between all rooms*/
  362.         for ( room = 0 ; room <= numRooms - 2 ; room++)
  363.             {
  364. /*We make a path from h1, h2, to h2, v2*/
  365.                 h1 = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  366.                 v1 = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  367.                 h2 = roomRect[room + 1].left + Rand(roomRect[room + 1].right - roomRect[room + 1].left + 1);
  368.                 v2 = roomRect[room + 1].top + Rand(roomRect[room + 1].bottom - roomRect[room + 1].top + 1);
  369.  
  370. /*First move along the h axis*/
  371.                 if ( h1 < h2 )
  372.                     for ( h = h1 ; h <= h2 ; h++)
  373.                             (**game).tileArray[h][v1] = empty;
  374.                 else
  375.                     for ( h = h1 ; h >= h2 ; h--)
  376.                             (**game).tileArray[h][v1] = empty;
  377. /*And then along the v axis*/
  378.                 if ( v1 < v2 )
  379.                     for ( v = v1 ; v <= v2 ; v++)
  380.                             (**game).tileArray[h2][v] = empty;
  381.                 else
  382.                     for ( v = v1 ; v >= v2 ; v--)
  383.                             (**game).tileArray[h2][v] = empty;
  384.             };
  385.  
  386. /*Now populate the rooms!*/
  387.         for ( room = 1 ; room <= numRooms - 1 ; room++)
  388.             {
  389.                 numTreasures = Rand(3);        /*0 to 2 treasures*/
  390.                 for ( i = 1 ; i <= numTreasures ; i++)
  391.                     {
  392.                         h = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  393.                         v = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  394.                         (**game).tileArray[h][v] = gold;
  395.                     };
  396.                 numMonsters = Rand(2);        /*0 to 1 monsters*/
  397.                 for ( i = 1 ; i <= numMonsters; i++)
  398.                     {
  399.                         h = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  400.                         v = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  401.                         (**game).tileArray[h][v] = enemy;
  402.                     };
  403.             };
  404.  
  405. /*Finally, place the player in the first room and the exit in the last. Also check that the exit is*/
  406. /*not the same position as the player! (It can happen, since we don't mak sure that rooms don't*/
  407. /*overlap!)*/
  408.  
  409. /*Player position:*/
  410.         (**game).playerPosition.h = roomRect[0].left + Rand(roomRect[0].right - roomRect[0].left + 1);
  411.         (**game).playerPosition.v = roomRect[0].top + Rand(roomRect[0].bottom - roomRect[0].top + 1);
  412.         (**game).tileArray[(**game).playerPosition.h][(**game).playerPosition.v] = player;
  413. /*Exit position:*/
  414.         h = roomRect[numRooms - 1].left + Rand(roomRect[numRooms - 1].right - roomRect[numRooms - 1].left + 1);
  415.         v = roomRect[numRooms - 1].top + Rand(roomRect[numRooms - 1].bottom - roomRect[numRooms - 1].top + 1);
  416. /*But please don't overwrite the player with the exit!*/
  417.         if ( h == (**game).playerPosition.h )
  418.             if ( v == (**game).playerPosition.v )
  419.                 {
  420. /*Try another room:*/
  421.                     h = roomRect[numRooms - 2].left + Rand(roomRect[numRooms - 2].right - roomRect[numRooms - 2].left + 1);
  422.                     v = roomRect[numRooms - 2].top + Rand(roomRect[numRooms - 2].bottom - roomRect[numRooms - 2].top + 1);
  423.  
  424. /*Still failure? Darn. Just take a space next to the player.*/
  425.                     if ( h == (**game).playerPosition.h )
  426.                         if ( v == (**game).playerPosition.v )
  427.                             if ( h < kArraySizeH )
  428.                                 h = h + 1;
  429.                             else
  430.                                 h = h - 1;
  431.                 };
  432. /*OK, that's enough. Set the exit!*/
  433.         (**game).tileArray[h][v] = exitPos;
  434.  
  435. /*All tiles are unknown when we start*/
  436.         for ( h = 0 ; h < kArraySizeH ; h++)
  437.             for ( v = 0 ; v < kArraySizeV ; v++)
  438.                 (**game).tileKnown[h][v] = false;
  439. /*…except the ones around the player*/
  440.  
  441. /*Make the spaces around the player known*/
  442.         ShowAroundPlayer();
  443.  
  444. /*Draw all tiles!*/
  445.         for ( h = 0 ; h < kArraySizeH ; h++)
  446.             for ( v = 0 ; v < kArraySizeV ; v++)
  447.                 DrawTile(h, v);
  448. } /*CreateLevel*/
  449.  
  450.  
  451. /* Move an enemy */
  452.  
  453. static void MoveEnemy(short h, short v)
  454. {
  455.         short dist;
  456.         short newh, newv;
  457.         short dir;
  458.  
  459. /*1: decide if we are close to enough to the player to "hear" the player*/
  460.  
  461.     dist = abs(h - (**game).playerPosition.h) + abs(v - (**game).playerPosition.v); /*City Block distance*/
  462.  
  463. /*2: Make a suggested destination, newh, newv*/
  464.  
  465.     if ( dist < Rand(15) )
  466.         { /*Move towards the player*/
  467.             if ( h < (**game).playerPosition.h )
  468.                 newh = h + 1;
  469.             else if ( h > (**game).playerPosition.h )
  470.                 newh = h - 1;
  471.             else
  472.                 newh = h;
  473.             if ( v < (**game).playerPosition.v )
  474.                 newv = v + 1;
  475.             else if ( v > (**game).playerPosition.v )
  476.                 newv = v - 1;
  477.             else
  478.                 newv = v;
  479.         }
  480.     else
  481.         { /*Move randomly*/
  482.             dir = Rand(8);
  483.             newh = h + directionTable[dir].h;
  484.             newv = v + directionTable[dir].v;
  485.         };
  486.  
  487. /*3: Check what is in the destination and take appropriate action (move, fight)*/
  488.  
  489.     switch ( (**game).tileArray[newh][newv] )
  490.         {
  491.         case empty: 
  492.                 (**game).tileArray[newh][newv] = tempEnemy;    /*We can't use "enemy", since then we might process it again in the same move!*/
  493.                 (**game).tileArray[h][v] = empty;
  494.                 DrawTile(newh, newv);
  495.                 DrawTile(h, v);
  496.                 break;
  497.         case player: 
  498.                 if ( Rand(10) > 5 )                            /*Does it hit?*/
  499.                     {                                        /*Enemy hits player!*/
  500.                         (**game).playerHitPoints--;                    /*Reduce player hit points*/
  501.                         if ( (**game).playerHitPoints > 0 )
  502.                             {                                        /*Player still lives!*/
  503.                                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer hit"), false) )
  504.                                     ;
  505.                             }
  506.                         else
  507.                             {                                /*Player died!*/
  508.                                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer died"), false) )
  509.                                     ;
  510.                             };
  511.                     }
  512.                 else
  513.                     {                                    /*Miss!*/
  514.                         if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer miss"), false) )
  515.                             ;
  516.                     };
  517.                 break;
  518.          case wall:
  519.         case enemy:
  520.         case gold:
  521.         case exitPos:
  522.         case tempEnemy: 
  523.             ;                                            /*Don't move to any of these!*/
  524.     }; /*case*/
  525. } /*MoveEnemy*/
  526.  
  527.  
  528. /* Initialize - create window, load graphics */
  529.  
  530. static void InitDungeon()
  531. {
  532.     Rect windowRectangle;
  533.  
  534. /*Set up the window*/
  535.         SetRect(&windowRectangle, 50, 50, 50 + kArraySizeH * kTileSizeH, 50 + kArraySizeV * kTileSizeV);
  536.         myWindow = NewCWindow(nil, &windowRectangle, "\pDungeon 3", true, 0, (WindowPtr)-1L, false, 0);
  537.         SetPort(myWindow);
  538.  
  539.         qd.randSeed = TickCount ();    /*Seed the random number generator*/
  540.  
  541. /*Load all pictures*/
  542.         floorTile = GetPicture(128);            /*PICT resource #128.*/
  543.         playerTile = GetPicture(129);            /*PICT resource #129.*/
  544.         enemyTile = GetPicture(130);            /*PICT resource #130.*/
  545.         goldTile = GetPicture(131);                /*PICT resource #131.*/
  546.         wallTile = GetPicture(132);                /*PICT resource #132.*/
  547.         exitTile = GetPicture(133);                /*PICT resource #133.*/
  548.  
  549. /* Allocate the game world */
  550.         game = (GameStateHnd)NewHandle(sizeof(GameState));
  551. } /*InitDungeon*/
  552.  
  553.  
  554. /* Set up for a new game */
  555.  
  556. static void NewGame()
  557. {
  558. /* Fill in the tileArr array */
  559.         CreateLevel();
  560.  
  561. /* Start with a healthy player */
  562.         (**game).playerHitPoints = 5;
  563. } /*NewGame*/
  564.  
  565.  
  566. /* ValidMove checks if a tile clickedTile is inside the array bounds *and* near the player */
  567.  
  568. static Boolean ValidMove(Point clickedTile)
  569. {
  570. /* Valid tile?*/
  571.     if ( clickedTile.h >= 0 )
  572.         if ( clickedTile.v >= 0 )
  573.             if ( clickedTile.h < kArraySizeH )
  574.                 if ( clickedTile.v < kArraySizeV )
  575. /* OK, we are inside the game area, clicking in some space! Is it next to the player?*/
  576.                     if ( clickedTile.h >= (**game).playerPosition.h - 1 )
  577.                         if ( clickedTile.h <= (**game).playerPosition.h + 1 )
  578.                             if ( clickedTile.v >= (**game).playerPosition.v - 1 )
  579.                                 if ( clickedTile.v <= (**game).playerPosition.v + 1 ) 
  580.                                     return true;
  581.     return false;
  582. } /*ValidMove*/
  583.  
  584.  
  585. /* Try to move the player to the position where we clicked. */
  586.  
  587. static void MovePlayer(Point clickedTile)
  588. {
  589. short h, v;
  590.  
  591. /* Valid move?*/
  592.     if (ValidMove(clickedTile))
  593. /* Yes! What is there? */
  594.     { 
  595.         switch ( (**game).tileArray[clickedTile.h][clickedTile.v] )
  596.             {
  597.             case empty:
  598.                     (**game).tileArray[(**game).playerPosition.h][(**game).playerPosition.v] = empty;
  599.                     (**game).tileArray[clickedTile.h][clickedTile.v] = player;
  600.                     DrawTile((**game).playerPosition.h, (**game).playerPosition.v);
  601.                     DrawTile(clickedTile.h, clickedTile.v);
  602.                     (**game).playerPosition = clickedTile;
  603.                     break;
  604.             case gold: 
  605.                     (**game).tileArray[(**game).playerPosition.h][(**game).playerPosition.v] = empty;
  606.                     (**game).tileArray[clickedTile.h][clickedTile.v] = player;
  607.                     DrawTile((**game).playerPosition.h, (**game).playerPosition.v);
  608.                     DrawTile(clickedTile.h, clickedTile.v);
  609.                     (**game).playerPosition = clickedTile;
  610. /* We could add score here */
  611.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pMoney"), false) )
  612.                         ;
  613.                     break;
  614.             case wall: 
  615.                 SysBeep(1);
  616.                 break;
  617.             case enemy: 
  618.                 if ( Rand(10) > 4 )
  619.                     { /*Hit! Play a "monster died" sound*/
  620.                         if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pEnemy died"), false) )
  621.                             ;
  622.                         /* Walk to the space where the monster was.*/
  623.                         (**game).tileArray[(**game).playerPosition.h][(**game).playerPosition.v] = empty;
  624.                         (**game).tileArray[clickedTile.h][clickedTile.v] = player;
  625.                         DrawTile((**game).playerPosition.h, (**game).playerPosition.v);
  626.                         DrawTile(clickedTile.h, clickedTile.v);
  627.                         (**game).playerPosition = clickedTile;
  628.                     }
  629.                 else
  630.                     { /*Miss! Play the "miss" sound. */
  631.                         if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pEnemy miss"), false) )
  632.                             ;
  633.                     };
  634.                 break;
  635.                 /*attack!*/
  636.             case exitPos: 
  637.                 (**game).tileArray[(**game).playerPosition.h][(**game).playerPosition.v] = empty;
  638.                 (**game).tileArray[clickedTile.h][clickedTile.v] = player;
  639.                 DrawTile((**game).playerPosition.h, (**game).playerPosition.v);
  640.                 DrawTile(clickedTile.h, clickedTile.v);
  641.                 (**game).playerPosition = clickedTile;
  642.                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pNext level"), false) )
  643.                     ;
  644.                 CreateLevel();    /* Don't quit - make a new level instead */
  645.                 break;
  646.         }; /*case*/
  647.     };
  648.  
  649. ShowAroundPlayer();
  650.  
  651. /* Monsters are allowed to move now!*/
  652.  
  653. /* Move all enemies!*/
  654. for ( h = 0 ; h < kArraySizeH ; h++)
  655.     for ( v = 0 ; v < kArraySizeV ; v++)
  656.         if ( (**game).tileArray[h][v] == enemy )
  657.             MoveEnemy(h, v);
  658. /* After moving, replace tempEnemy by enemy.*/
  659. for ( h = 0 ; h < kArraySizeH ; h++)
  660.     for ( v = 0 ; v < kArraySizeV ; v++)
  661.         if ( (**game).tileArray[h][v] == tempEnemy )
  662.             (**game).tileArray[h][v] = enemy;
  663. } /*MovePlayer*/
  664.  
  665.  
  666. /* Handle mouse downs */
  667.  
  668. static void DoMouse(Point clickPoint, long mods)
  669. {
  670.     Point clickedTile;
  671.     short h, v;
  672.  
  673. /* If the hero is dead, we can't move! */
  674.     if ( (**game).playerHitPoints < 1 )
  675.     {
  676.         SysBeep(1);
  677.         return;
  678.     };
  679.  
  680. /*Convert clickPoint to local coordinates*/
  681.     GlobalToLocal(&clickPoint);
  682. /*Convert it to coordinates in the arrays.*/
  683.     clickedTile.h = clickPoint.h / kTileSizeH;
  684.     clickedTile.v = clickPoint.v / kTileSizeV;
  685.     MovePlayer(clickedTile);
  686. } /*DoMouse*/
  687.  
  688.  
  689. /* Empty stub for a background task. */
  690.  
  691. static void DoBackground()
  692. {
  693. } /*DoBackground*/
  694.  
  695.  
  696. /* Add two points - simplifies the keydown handler */
  697.  
  698. static Point AddPoints(Point p1,Point p2)
  699. {
  700.     Point dest;
  701.  
  702.     dest.h = p1.h + p2.h;
  703.     dest.v = p1.v + p2.v;
  704.     return dest;
  705. }; /*AddPoints*/
  706.  
  707.  
  708. /* Handle key downs */
  709.  
  710. static void DoKey(char theKey, long mods)
  711. {
  712. /* If the hero is dead, we can't move! */
  713.     if ( (**game).playerHitPoints < 1 )
  714.         {
  715.             SysBeep(1);
  716.             return;
  717.         };
  718.  
  719. /* For now, we use a hard-coded key mapping. */
  720.     switch ( theKey )
  721.         {
  722.         case 'd':
  723.         case '6': 
  724.             MovePlayer(AddPoints((**game).playerPosition, directionTable[0]));break;
  725.         case 'e':
  726.         case '9': 
  727.             MovePlayer(AddPoints((**game).playerPosition, directionTable[1]));break;
  728.         case 'w':
  729.         case '8': 
  730.             MovePlayer(AddPoints((**game).playerPosition, directionTable[2]));break;
  731.         case 'q':
  732.         case '7': 
  733.             MovePlayer(AddPoints((**game).playerPosition, directionTable[3]));break;
  734.         case 'a':
  735.         case '4': 
  736.             MovePlayer(AddPoints((**game).playerPosition, directionTable[4]));break;
  737.         case 'z':
  738.         case '1': 
  739.             MovePlayer(AddPoints((**game).playerPosition, directionTable[5]));break;
  740.         case 'x':
  741.         case '2': 
  742.             MovePlayer(AddPoints((**game).playerPosition, directionTable[6]));break;
  743.         case 'c':
  744.         case '3': 
  745.             MovePlayer(AddPoints((**game).playerPosition, directionTable[7]));break;
  746.         default:
  747.             SysBeep(1);
  748.     }; /*case*/
  749. }; /*DoKey*/
  750.  
  751.  
  752. /* Handle selections in the File menu */
  753.  
  754. static void DoFileMenu(short item)
  755. {
  756.     switch ( item )
  757.         {
  758.         case 1: 
  759.             NewGame();break;        /* New */
  760.         case 2: 
  761.             OpenGame(true);break;    /* Open */
  762.         case 3: 
  763.             SaveGame(false);break;    /* Save */
  764.         case 4: 
  765.             SaveGame(true);break;    /* Save as… */
  766.         case 6: 
  767.             gDone = true;break;        /* Quit */
  768.     }; /*case*/
  769. }; /*DoFileMenu*/
  770.  
  771.  
  772. /* The "About" item was selected */
  773.  
  774. static void DoAbout()
  775. {
  776.     short ignore;
  777.  
  778.     ignore = Alert(128, nil);
  779. }; /*DoAbout*/
  780.  
  781.  
  782. /* Handle update events */
  783.  
  784. static void DoUpdate()
  785. {
  786.         short h, v;
  787.  
  788.         BeginUpdate(myWindow);
  789. /*Draw all tiles!*/
  790.         for ( h = 0 ; h < kArraySizeH ; h++)
  791.             for ( v = 0 ; v < kArraySizeV ; v++)
  792.                 DrawTile(h, v);
  793.         EndUpdate(myWindow);
  794. }; /*DoUpdate*/
  795.  
  796.  
  797. /* Handle menu selections */
  798.  
  799. static void DoMenuSelection(long mSelect)
  800. {
  801.     short menuID;
  802.     short menuItem;
  803.     GrafPtr savePort;
  804.     Str255 name;
  805.     short ignore;
  806.  
  807.     menuID = HiWord(mSelect);
  808.     menuItem = LoWord(mSelect);
  809.  
  810.     switch ( menuID )
  811.         {
  812.         case appleID: 
  813.             if ( menuItem == 1 )
  814.                 DoAbout();
  815.             else
  816.                 {
  817.                     GetPort(&savePort);
  818.                     GetItem(GetMHandle(appleID), menuItem, name);
  819.                     ignore = OpenDeskAcc(name);
  820.                     SetPort(savePort);
  821.                 };
  822.             break;
  823.         case fileID: 
  824.             DoFileMenu(menuItem);break;
  825.         default:
  826.     ;}; /*case*/
  827.     HiliteMenu(0);
  828. }; /*DoMenuSelection*/
  829.  
  830.  
  831. /* Set up menus */
  832.  
  833. static void SetupMenus()
  834. {
  835.     MenuHandle appleMenu, fileMenu;
  836.     Str255 tempStr;
  837.  
  838.     tempStr[0]=1; tempStr[1] = (char)20;
  839.     appleMenu = NewMenu(appleID, tempStr); /*Apple menu symbol*/
  840.     InsertMenu(appleMenu, 0);
  841.     AppendMenu(appleMenu, "\pAbout Dungeon…;(-");
  842.     AddResMenu(appleMenu, 'DRVR');
  843.  
  844.     fileMenu = GetMenu(128);
  845.     InsertMenu(fileMenu, 0);
  846.     DrawMenuBar ();
  847.  
  848. }; /*SetupMenus*/
  849.  
  850.  
  851. /* Main event loop */
  852.  
  853. static void MainLoop(void)
  854. {
  855. #define    kSleep 5 /*Real programs may modify the sleep time depending on whether or not they are in the front*/
  856.  
  857.     EventRecord theEvent;
  858.     char theKey;
  859.     long whatSelection;
  860.     short whichPart;
  861.     WindowPtr whichWindow;
  862.     Rect r;
  863.  
  864. /*Get the next event with WaitNextEvent.*/
  865.     if ( WaitNextEvent(everyEvent, &theEvent, kSleep, nil) )
  866.         switch ( theEvent.what )
  867.         {
  868.             case mouseDown: 
  869. /*We must find out what kind of mouse down this was.*/
  870.                 whichPart = FindWindow(theEvent.where, &whichWindow);
  871.                 switch ( whichPart )
  872.                 {
  873.                     case inMenuBar:
  874. /*Click in menu bar. Let the system call MenuSelect track it.*/
  875.                         whatSelection = MenuSelect(theEvent.where);
  876.                         DoMenuSelection(whatSelection);    /*Our own routine for handling menu selections*/
  877.                         break;
  878.                     case inSysWindow:
  879. /*Click in some window that isn't ours*/
  880.                         SystemClick(&theEvent, whichWindow);
  881.                         break;
  882.                     case inGoAway:
  883. /*Click in close box of window. For "desk accessory"-style games, that is a good quit signal*/
  884.                         if ( (TrackGoAway(whichWindow, theEvent.where)) )
  885.                             gDone = true;
  886.                         break;
  887.                     case inDrag:
  888. /*Drag a window*/
  889.                         if ( (whichWindow != FrontWindow ()) && ((theEvent.modifiers, cmdKey) == 0) )
  890.                             SelectWindow(whichWindow);
  891.                         DragWindow(whichWindow, theEvent.where, &qd.screenBits.bounds);
  892.                         break;
  893.                     case inGrow: 
  894.                         ;  /*Ignored - we don't resize*/
  895.                         break;
  896.                     case inContent:
  897. /*Click in the window.*/
  898.                         if ( (whichWindow != FrontWindow ()) )
  899.                             SelectWindow(whichWindow);
  900.                         else
  901.                             DoMouse(theEvent.where, theEvent.modifiers); /*Go to application-specific mouse down handling*/
  902.                 }; /*case whichPart*/
  903.                 break; /*mouseDown*/
  904.             case keyDown:
  905.             case autoKey:
  906. /*If the command key is pressed, it is a menu selection*/
  907.                 theKey = (char)(theEvent.message & charCodeMask);
  908.                 if ( ((theEvent.modifiers & cmdKey) != 0) )
  909.                     DoMenuSelection(MenuKey(theKey));    /*Our own routine for handling menu selections*/
  910.                 else
  911. /*Otherwise, it's a normal key down.*/
  912.                     DoKey(theKey, theEvent.modifiers);
  913.                 break;
  914.             case updateEvt:
  915. /*Find out what event the update event is for.*/
  916.                 if ( (WindowPtr)theEvent.message == myWindow )
  917.                     DoUpdate();
  918.                 break;
  919.             case kHighLevelEvent: 
  920.                 if (AEProcessAppleEvent(&theEvent) != noErr)
  921.                 { /* error - ignored */ };
  922.                 break;
  923.  
  924.             default: /*Other events are ignored*/
  925.                 ;
  926.         } /*case theEvent.what*/
  927.  
  928. /*For each turn to the event loop, we may to call some "background" process, e.g. animations.*/
  929.     DoBackground();
  930. } /*MainLoop*/
  931.  
  932.  
  933. /* Standard inits */
  934.  
  935. static void InitToolbox(void) {
  936.     InitGraf (&qd.thePort);
  937.     InitFonts ();
  938.     FlushEvents (everyEvent,0);
  939.     InitWindows ();
  940.     InitMenus ();
  941.     TEInit ();
  942.     InitDialogs (nil);
  943.     InitCursor ();
  944. }
  945.  
  946. /* Main program */
  947.  
  948. void main(void)
  949. {
  950.     InitToolbox();
  951.     AppleEventInit();
  952.     InitDungeon();
  953.     SetupMenus();
  954.     NewGame();
  955. /*Initializations done! Run the game loop until the game ends.*/
  956.     do
  957.         {
  958.         MainLoop();
  959.     } while (!  gDone);
  960. } /*Dungeon*/
  961.  
  962.  
  963. /*What's left for making a real game of it?*/
  964. /*- Animations*/
  965. /*- Faster drawing*/
  966. /*- Asynch sound*/
  967. /*- More objects, i.e. weapons, monsters, treasures…*/